/**
 *    Copyright 2009-2021 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;

/**
 * 1.解析 SQL 语句中的“#{}”占位符中定义的属性
 * 2.将“#{}”占位符替换成“？”占位符
 * @author Clinton Begin
 */
public class SqlSourceBuilder extends BaseBuilder {

	private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

	public SqlSourceBuilder(Configuration configuration) {
		super(configuration);
	}

	/**
	 * SqlSourceBuilder的核心方法
	 * @param originalSql 经过 SqlNode.apply() 方法处理之后的 SQL 语句
	 * @param parameterType 用户传入实参的类型
	 * @param additionalParameters 记录了形成与实参的对应关系(经过 SqlNode.apply() 方法处理后 DynamicContext.bindings 集合)
	 * @return
	 */
	public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
		ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType,
				additionalParameters);
		// 创建分词解析器
		GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
		// 解析#{}
		String sql = parser.parse(originalSql);
		// 将解析之后的SQL信息，封装到StaticSqlSource对象中
		return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
	}

	/**
	 * 解析“#{}”占位符中的参数属性以及替换占位符的核心
	 */
	private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

		/** 记录解析得到的 ParameterMapping 集合 */
		private List<ParameterMapping> parameterMappings = new ArrayList<>();
		/** 参数类型 */
		private Class<?> parameterType;
		/** DynamicContext.bindings 集合对应的 MetaObject 对象 */
		private MetaObject metaParameters;

		public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType,
				Map<String, Object> additionalParameters) {
			super(configuration);
			this.parameterType = parameterType;
			this.metaParameters = configuration.newMetaObject(additionalParameters);
		}

		public List<ParameterMapping> getParameterMappings() {
			return parameterMappings;
		}

		@Override
		public String handleToken(String content) {
			// 创建 ParameterMapping 对象，并添加到 ParameterMappings 集合中保存
			parameterMappings.add(buildParameterMapping(content));
			// 返回问好占位符
			return "?";
		}

		/**
		 * 创建 ParameterMapping 对象,负责解析参数属性
		 * @param content 实参名称
		 */
		private ParameterMapping buildParameterMapping(String content) {
			// 解析参数的属性，并添加到 Map。
			Map<String, String> propertiesMap = parseParameterMapping(content);
			// 获取参数名称
			String property = propertiesMap.get("property");
			Class<?> propertyType;
			// 确定参数的 javaType 类型
			if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
				propertyType = metaParameters.getGetterType(property);
			} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
				propertyType = parameterType;
			} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
				propertyType = java.sql.ResultSet.class;
			} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
				propertyType = Object.class;
			} else {
				MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
				if (metaClass.hasGetter(property)) {
					propertyType = metaClass.getGetterType(property);
				} else {
					propertyType = Object.class;
				}
			}
			// 创建 ParameterMapping 的建造者，并设置 ParameterMapping 相关配置
			ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
			Class<?> javaType = propertyType;
			String typeHandlerAlias = null;
			for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
				String name = entry.getKey();
				String value = entry.getValue();
				if ("javaType".equals(name)) {
					javaType = resolveClass(value);
					builder.javaType(javaType);
				} else if ("jdbcType".equals(name)) {
					builder.jdbcType(resolveJdbcType(value));
				} else if ("mode".equals(name)) {
					builder.mode(resolveParameterMode(value));
				} else if ("numericScale".equals(name)) {
					builder.numericScale(Integer.valueOf(value));
				} else if ("resultMap".equals(name)) {
					builder.resultMapId(value);
				} else if ("typeHandler".equals(name)) {
					typeHandlerAlias = value;
				} else if ("jdbcTypeName".equals(name)) {
					builder.jdbcTypeName(value);
				} else if ("property".equals(name)) {
					// Do Nothing
				} else if ("expression".equals(name)) {
					throw new BuilderException("Expression based parameters are not supported yet");
				} else {
					throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content
							+ "}.  Valid properties are " + parameterProperties);
				}
			}
			// 获取 typeHandler 对象
			if (typeHandlerAlias != null) {
				builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
			}
			// 创建 ParameterMapping 对象，注意：如果没有指定 typeHandler，则会在这里的build() 方法中，根据 javaType 和 jdbcType
			// 从 TypeHandlerRegistry 中获取对应的 typeHandler 对象
			return builder.build();
		}

		/**
		 *  解析参数的属性，并添加到 Map。
		 *  <pre>
		 *      Eg:
		 *      	#{_frc_item_0, javaType=int, jdbcType=NUMERIC, typeHandler=MyTypeHandler} 这个占位符
		 *      解析成如下 Map
		 *      	{"property" -> "_frch_item_0","javaType" -> "int", "jdbcType" -> "NUMERIC"
		 *      	, "typeHandler" -> "MyTypeHandler"}
		 *  </pre>
		 * @param content
		 * @return
		 */
		private Map<String, String> parseParameterMapping(String content) {
			try {
				return new ParameterExpression(content);
			} catch (BuilderException ex) {
				throw ex;
			} catch (Exception ex) {
				throw new BuilderException("Parsing error was found in mapping #{" + content
						+ "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
			}
		}
	}

}
